import ctypes
import time,os
import tempfile
import numpy as np
import urllib.request

from itertools import cycle
from vispy import app, scene, io
from vispy.color import get_colormaps, BaseColormap
from vispy.visuals.transforms import STTransform
from vispy.util import logger, Frozen
from vispy import gloo 

ll = ctypes.cdll.LoadLibrary
current_path = os.path.dirname(__file__)
import os
 
if os.name == 'nt':
    print("Windows")
    dllpath = os.path.join(current_path, "./plugins/fnt_libpy.dll")
    print(dllpath)
    imageLoader = ctypes.WinDLL(dllpath)
elif os.name == 'posix':
    print("Linux")
    dllpath = os.path.join(current_path, "./plugins/libfnt_libpy.so")

    print(dllpath)
    imageLoader = ll(dllpath)
else:
    print("Unknown OS")



imageLoader.create.restype = ctypes.POINTER(ctypes.c_int)
class FNTCube:


    def __init__(self) -> None:
        self.sampleid='221473'
        self.server='http://10.10.48.110'
        self.path='data'
        self.imageCoord=[]
        self.mouseCoord=[]
        self.cubeIndex=[]
        self.pixelSpace=[0.325,0.325,1]
        self.radiu=3
        self.cubesize=[]
        self.imagesize=[]
        self.volume = None
        pass
    def getVolumeFromIndex(self,x,y,z):
        url = self.server+'/'+self.path+'/'+self.sampleid+'/ch00hevc/z'+str(z).zfill(8)+'/y'+str(y).zfill(8)+'.x'+str(x).zfill(8)+'.hevc'
        response=urllib.request.urlopen(url)
        buf=response.read()
        
        with tempfile.NamedTemporaryFile(suffix = '.hevc',delete=False) as fp:
            fp.write(buf)
            fp.close()
            print(fp.name)
            handler = imageLoader.create(bytes(fp.name, encoding = "utf8"))
    
            # print(handler)
            w = ctypes.c_longlong(0)
            h = ctypes.c_longlong(0)
            d = ctypes.c_longlong(0)
            imageLoader.getSizes.restype = ctypes.c_bool
            tag = imageLoader.getSizes(handler,ctypes.byref(w),ctypes.byref(h),ctypes.byref(d))
            # print(tag,w,h,d)
            imageLoader.close(handler)
            ptr = (ctypes.c_char*(d.value*w.value*h.value*2))()

            x0 = ctypes.c_long(0)
            y0 = ctypes.c_long(0)
            z0 = ctypes.c_long(0)
            ystride = ctypes.c_longlong(w.value*2)
            zstride = ctypes.c_longlong(w.value*h.value*2)
            imageLoader.readData.restype = ctypes.c_bool
            tag = imageLoader.readData(handler,ptr,x0,y0,z0,ystride,zstride)
            tmp = np.frombuffer(ptr, dtype=np.uint16)
            self.volume = tmp.reshape(d.value,w.value,h.value)
            #fp.close()
            os.remove(fp.name)
            return self.volume
    def getVolume(self):

        rangei = self.radiu if self.radiu<4 else 3

        sj = (self.radiu-1)*2+1
        si = (rangei-1)*2+1
        sk = (rangei-1)*2+1
        volume = np.zeros((self.cubesize[2]*si,self.cubesize[1]*sj,self.cubesize[0]*sk))
        progress =0
        self.startIndex=[self.cubeIndex[0]+(1-rangei)*self.cubesize[0],self.cubeIndex[1]+(1-self.radiu)*self.cubesize[1],self.cubeIndex[2]+(1-rangei)*self.cubesize[2]]
        for i in range(1-rangei,rangei):
            for j in range(1-self.radiu,self.radiu):
                for k in range(1-rangei,rangei):
                    vol = self.getVolumeFromIndex(self.cubeIndex[0]+i*self.cubesize[0],self.cubeIndex[1]+j*self.cubesize[1],self.cubeIndex[2]+k*self.cubesize[2])
                    volume[(rangei-1+k)*self.cubesize[2]:(rangei+k)*self.cubesize[2],
                    (self.radiu-1+j)*self.cubesize[1]:(self.radiu+j)*self.cubesize[1],
                    (rangei-1+i)*self.cubesize[0]:(rangei+i)*self.cubesize[0]] = vol
                    progress+=1
                    print('finished '+str(progress)+'/'+str(si*sj*sk))
        # return self.getVolumeFromIndex(self.cubeIndex[0],self.cubeIndex[1],self.cubeIndex[2])
        self.volume=volume
        return volume
        
    def phraseCatalog(self):
        url=self.server+'/'+self.path+'/'+self.sampleid+'/'+'catalog'
        response=urllib.request.urlopen(url)
        buf=bytes.decode(response.read()).lower()
        channels = buf.split('[ch00')
        for channel in channels:
            if channel.find('hevc')>=0:
                hevcprops = channel.splitlines()
                for prop in hevcprops:
                    if prop.find('size')==0:
                        self.imagesize = [int(x) for x in prop.split('=')[1].split(' ')]
                    if prop.find('cubesize')==0:
                        self.cubesize = [int(x) for x in prop.split('=')[1].split(' ')]
                    if prop.find('direction')==0:
                        self.pixelSpace = [float(x) for x in prop.split('=')[1].split(' 0 0 0 ')]
        pass
    def setSampleID(self,sampleid):
        self.sampleid = sampleid
        self.phraseCatalog()

    def setMouseCoord(self,x,y,z):
        self.mouseCoord=[x,y,z]
        self.imageCoord = [int(x/self.pixelSpace[0]),int(y/self.pixelSpace[1]),int(z/self.pixelSpace[2])]
        self.cubeIndex = [self.imageCoord[0]//self.cubesize[0]*self.cubesize[0],self.imageCoord[1]//self.cubesize[1]*self.cubesize[1],self.imageCoord[2]//self.cubesize[2]*self.cubesize[2]]
        pass
    def setRadiu(self,radiu=1):
        self.radiu = radiu
        pass

    def physicalPoint2LocalPoint(self,physicalPoints,isImageCoord=False):
        shape = self.volume.shape
        pos=[]
        for point in physicalPoints:
            x=point[0]
            y=point[1]
            z=point[2]
            imageCoord = [int(x/self.pixelSpace[0]),int(y/self.pixelSpace[1]),int(z/self.pixelSpace[2])]
            # imageCoord = cube.imageCoord

            localPoint = [imageCoord[0] - self.startIndex[0],imageCoord[1] - self.startIndex[1],imageCoord[2] - self.startIndex[2]]
            pointtemp = [(localPoint[0]-shape[2]/2)*self.pixelSpace[0],(localPoint[1]-shape[1]/2)*self.pixelSpace[1],(localPoint[2]-shape[0]/2)*self.pixelSpace[2]]
            if isImageCoord:
                pos.append(localPoint)
            else:
                pos.append(pointtemp)
        return pos

class VolumeRender(app.Canvas):

    def __init__(self,size=(800,600)) -> None:
        # app.Canvas.__init__(self, size=size, title='Neuron Render',keys='interactive')
        self.Canvas = scene.SceneCanvas(keys='interactive',title='FNTCube Render', size=(800, 600), show=True)
        # self.Canvas.events.connect(self.on_mouse_move)
        self.Canvas.events.mouse_move.connect(self.on_mouse_move)
        self.Canvas.events.key_press.connect(self.on_key_press)
        pass
    def setClim(self,clim):
        self.volume1.clim=clim
    def CubeImageCoord2GlobalPhysicalCoord(self,cubeImageCoord):
        #check input in cube volume
        shape = self.volume1._vol_shape
        if cubeImageCoord[0]>=shape[0] or cubeImageCoord[1]>=shape[1] or cubeImageCoord[2]>=shape[2] :
            return
        pass
    def getVoxelValue(self,points):
        shape = self.volume1._vol_shape
        # shape = [shape[2]*cube.pixelSpace[0]/2,shape[1]*cube.pixelSpace[1]/2,shape[0]/2]
        
        self.point = scene.visuals.Markers(parent=self.view.scene,size=0.2,scaling=True)
        pos = []
        values=[]
        for point in points:
            x=point[0]
            y=point[1]
            z=point[2]
            imageCoord = [int(x/self.cube.pixelSpace[0]),int(y/self.cube.pixelSpace[1]),int(z/self.cube.pixelSpace[2])]
            localPoint = [imageCoord[0] - self.cube.startIndex[0],imageCoord[1] - self.cube.startIndex[1],imageCoord[2] - self.cube.startIndex[2]]
            value = self.volume1._last_data[localPoint[2]][localPoint[1]][localPoint[0]]
            values.append(value)
        return values

    def drawSegments(self,segments):
        shape = self.volume1._vol_shape
        # shape = [shape[2]*cube.pixelSpace[0]/2,shape[1]*cube.pixelSpace[1]/2,shape[0]/2]
        
        self.point = scene.visuals.Line(parent=self.view.scene,connect='segments')
        pos = []
        # pos.append(lineSegments[0])
        for lineSegments in segments:
            for i in range(len(lineSegments)):
                point = lineSegments[i]
                x=point[0]
                y=point[1]
                z=point[2]
                imageCoord = [int(x/self.cube.pixelSpace[0]),int(y/self.cube.pixelSpace[1]),int(z/self.cube.pixelSpace[2])]
                # imageCoord = cube.imageCoord

                localPoint = [imageCoord[0] - self.cube.startIndex[0],imageCoord[1] - self.cube.startIndex[1],imageCoord[2] - self.cube.startIndex[2]]
                pointtemp = [(localPoint[0]-shape[2]/2)*self.cube.pixelSpace[0],(localPoint[1]-shape[1]/2)*self.cube.pixelSpace[1],(localPoint[2]-shape[0]/2)*self.cube.pixelSpace[2]]
                if i==0 or i==len(lineSegments)-1:
                    pos.append(pointtemp)
                    continue
                pos.append(pointtemp)
                pos.append(pointtemp)
        # pos.append(lineSegments[len(lineSegments)-1])
        
        self.point.set_data(pos=np.array(pos))
        s = STTransform(translate=self.cam3.center)
        affine = s.as_matrix()
        self.point.transform = affine
        self.point.set_gl_state(depth_test=False)
        self.point.antialias = True
        pass
    def drawLines(self,lineSegments):
        shape = self.volume1._vol_shape
        # shape = [shape[2]*cube.pixelSpace[0]/2,shape[1]*cube.pixelSpace[1]/2,shape[0]/2]
        
        self.point = scene.visuals.Line(parent=self.view.scene,connect='segments')
        pos = []
        # pos.append(lineSegments[0])
        for i in range(len(lineSegments)):
            point = lineSegments[i]
            x=point[0]
            y=point[1]
            z=point[2]
            imageCoord = [int(x/self.cube.pixelSpace[0]),int(y/self.cube.pixelSpace[1]),int(z/self.cube.pixelSpace[2])]
            # imageCoord = cube.imageCoord

            localPoint = [imageCoord[0] - self.cube.startIndex[0],imageCoord[1] - self.cube.startIndex[1],imageCoord[2] - self.cube.startIndex[2]]
            pointtemp = [(localPoint[0]-shape[2]/2)*self.cube.pixelSpace[0],(localPoint[1]-shape[1]/2)*self.cube.pixelSpace[1],(localPoint[2]-shape[0]/2)*self.cube.pixelSpace[2]]
            if i==0 or i==len(lineSegments)-1:
                pos.append(pointtemp)
                continue
            pos.append(pointtemp)
            pos.append(pointtemp)
        # pos.append(lineSegments[len(lineSegments)-1])
        
        self.point.set_data(pos=np.array(pos))
        s = STTransform(translate=self.cam3.center)
        affine = s.as_matrix()
        self.point.transform = affine
        self.point.set_gl_state(depth_test=False)
        self.point.antialias = True


    


    def drawPoints(self,points):
        self.point = scene.visuals.Markers(parent=self.view.scene,size=0.02,scaling=False)
        pos=self.cube.physicalPoint2LocalPoint(points)
        self.point.set_data(pos=np.array(pos))
        s = STTransform(translate=self.cam3.center)
        affine = s.as_matrix()
        self.point.transform = affine
        self.point.set_gl_state(depth_test=False)
        self.point.antialias = True
        pass

    def setFNTCube(self,cube):
        self.cube=cube
        # vol1 = np.load(io.load_data_file('brain/mri.npz'))['data']
        vol1 =cube.getVolume()
        # vol1[vol1>vol1.mean()*20]=vol1.mean()*20
        # print(vol1.min(),vol1.mean(),vol1.max())
        # Prepare canvas
        self.Canvas.measure_fps()

        # Set up a viewbox to display the image with interactive pan/zoom
        self.view = self.Canvas.central_widget.add_view()

        # Create the volume visuals, only one is visible
        self.volume1 = scene.visuals.Volume(vol1, parent=self.view.scene, threshold=0.025)
        self.volume1.transform = scene.STTransform(translate=(0,0,0),scale=(cube.pixelSpace[0],cube.pixelSpace[1],cube.pixelSpace[2], 1))
        # self.volume1.transform = scene.STTransform(translate=(256*((cube.radiu-1)*2+1)*cube.pixelSpace[0],256*((cube.radiu-1)*2+1)*cube.pixelSpace[0],0),scale=(cube.pixelSpace[0],cube.pixelSpace[1],cube.pixelSpace[2], 1))

        # Create three cameras (Fly, Turntable and Arcball)
        fov = 60.
        self.cam1 = scene.cameras.FlyCamera(parent=self.view.scene, fov=fov, name='Fly')
        self.cam2 = scene.cameras.TurntableCamera(parent=self.view.scene, fov=fov,
                                            name='Turntable')
        self.cam3 = scene.cameras.ArcballCamera(parent=self.view.scene, fov=fov, name='Arcball')
        self.view.camera = self.cam3  # Select Arcball at first
        self.cam3.center = [self.cam3.center[0]*cube.pixelSpace[0],self.cam3.center[1]*cube.pixelSpace[1],self.cam3.center[2]]

        # # Create an XYZAxis visual
        # self.axis = scene.visuals.XYZAxis(parent=self.view.scene)
        # # s = STTransform(translate=(128*((cube.radiu-1)*2+1),128*((cube.radiu-1)*2+1),((cube.radiu-1)*2+1)*45), scale=(10, 10, 10, 1))
        # s = STTransform(translate=self.cam3.center, scale=(10, 10, 10, 1))
        # affine = s.as_matrix()
        # self.axis.transform = affine
        # self.axis.set_gl_state(depth_test=False)
        # self.axis.antialias = True
        shape = self.volume1._vol_shape
        shape = [shape[2]*cube.pixelSpace[0]/2,shape[1]*cube.pixelSpace[1]/2,shape[0]/2]
        self.point = scene.visuals.Line(parent=self.view.scene,pos=np.array([[-shape[2],-shape[1],-shape[0]],[shape[2],-shape[1],-shape[0]],[shape[2],shape[1],-shape[0]],[shape[2],shape[1],shape[0]]]))
        s = STTransform(translate=self.cam3.center)
        affine = s.as_matrix()
        self.point.transform = affine
        self.point.set_gl_state(depth_test=False)
        self.point.antialias = True
        
        self.drawPoints([cube.mouseCoord])
        print(self.getVoxelValue([cube.mouseCoord]))

        # create colormaps that work well for translucent and additive volume rendering
        class TransFire(BaseColormap):
            glsl_map = """
            vec4 translucent_fire(float t) {
                return vec4(pow(t, 0.5), t, t*t, max(0, t*1.05 - 0.05));
            }
            """


        class TransGrays(BaseColormap):
            glsl_map = """
            vec4 translucent_grays(float t) {
                return vec4(t*2, t*2, t*2, t*0.05);
            }
            """

        # Setup colormap iterators
        self.opaque_cmaps = cycle(get_colormaps())
        self.translucent_cmaps = cycle([TransFire(), TransGrays()])
        self.opaque_cmap = next(self.opaque_cmaps)
        self.translucent_cmap = next(self.translucent_cmaps)

        self.interp_methods = cycle(self.volume1.interpolation_methods)
        interp = next(self.interp_methods)
        self.Canvas.show()
        # self.show()


    # Implement axis connection with cam2
    # @canvas.events.mouse_move.connect
    # @app.Canvas.events.key_press.connect
    def on_mouse_move(self,event):
        if event.button == 1 and event.is_dragging:
            return
            self.axis.transform.reset()
            rot, x, y, z = self.cam3._quaternion.get_axis_angle()
            self.axis.transform.rotate(180 * rot / np.pi, (x, z, y))

            # self.axis.transform.rotate(self.cam2.roll, (0, 0, 1))
            # self.axis.transform.rotate(self.cam2.elevation, (1, 0, 0))
            # self.axis.transform.rotate(self.cam2.azimuth, (0, 1, 0))

            # self.axis.transform.scale((50, 50, 0.001))
            # self.axis.transform.translate((50., 50.))
            self.axis.transform.scale((50, 50, 1, 0.5))
            self.axis.transform.translate((90*((1-1)*2+1),90*((1-1)*2+1),0))
            
            self.axis.visible = True
            self.axis.update()


    # Implement key presses
    # @Canvas.events.key_press.connect
    def on_key_press(self,event):
        self.opaque_cmap, self.translucent_cmap
        if event.text == '1':
            cam_toggle = {self.cam1: self.cam2, self.cam2: self.cam3, self.cam3: self.cam1}
            self.view.camera = cam_toggle.get(self.view.camera, self.cam3)
            print(self.view.camera.name + ' camera')
            if self.view.camera is self.cam2:
                self.axis.visible = True
            else:
                self.axis.visible = False
        elif event.text == '2':
            methods = ['mip', 'translucent', 'iso', 'additive']
            method = methods[(methods.index(self.volume1.method) + 1) % 4]
            print("Volume render method: %s" % method)
            cmap = self.opaque_cmap if method in ['mip', 'iso'] else self.translucent_cmap
            self.volume1.method = method
            self.volume1.cmap = cmap

        elif event.text == '3':
            self.volume1.visible = not self.volume1.visible
            # volume2.visible = not volume1.visible
        elif event.text == '4':
            if self.volume1.method in ['mip', 'iso']:
                cmap = self.opaque_cmap = next(self.opaque_cmaps)
            else:
                cmap = self.translucent_cmap = next(self.translucent_cmaps)
            self.volume1.cmap = cmap
            # volume2.cmap = cmap
        elif event.text == '5':
            interp = next(self.interp_methods)
            self.volume1.interpolation = interp
            # volume2.interpolation = interp
            print(f"Interpolation method: {interp}")
        elif event.text == '6':
            self.volume1.clim=(self.volume1._texture._clim[0]+10,self.volume1._texture._clim[1]+10)
            print(self.volume1.clim)
        elif event.text == '7':
            self.volume1.clim=(self.volume1._texture._clim[0]-10,self.volume1._texture._clim[1]-10)
            print(self.volume1.clim)
        elif event.text == '8':
            self.volume1.clim=(self.volume1._texture._clim[0]-10,self.volume1._texture._clim[1]+10)
            print(self.volume1.clim)
        elif event.text == '9':
            self.volume1.clim=(self.volume1._texture._clim[0]+10,self.volume1._texture._clim[1]-10)
            print(self.volume1.clim)
            # self.cam3.set_range()
        elif event.text == '0':
            self.cam1.set_range()
            self.cam3.set_range()
        elif event.text != '' and event.text in '[]':
            s = -0.025 if event.text == '[' else 0.025
            self.volume1.threshold += s
            # volume2.threshold += s
            th = self.volume1.threshold if self.volume1.visible else self.volume1.threshold
            print("Isosurface threshold: %0.3f" % th)


if __name__ == '__main__':
    import vispy.io as io
    cube = FNTCube()
    cube.setSampleID('210726')
    # cube.setMouseCoord(6719.08,2002.89,4665.78) # cortex
    # cube.setMouseCoord(6220.28,3380.16,6409.23) # th
    # cube.setMouseCoord(6016.28,3820.16,6315.23) # th
    cube.setMouseCoord(3316.73,4630.76,5811.05) # th
    cube.radiu=1
    volume =cube.getVolume()
    # cube.setMouseCoord(3459.95,1743.75,6370.94) #mean=137
    
    render = VolumeRender()
    render.setFNTCube(cube)
    render.volume1.clim=(20,1000)
    app.run()
    
